package com.jintian.smart.kernel.orm.query;

import java.util.Arrays;
import java.util.Collections;

import com.jintian.smart.kernel.common.util.StringUtil;
import com.jintian.smart.kernel.orm.dto.Condition;
import com.jintian.smart.kernel.orm.entity.ColumnType;
import org.apache.lucene.search.join.ScoreMode;
import org.bson.conversions.Bson;
import org.elasticsearch.index.query.MatchQueryBuilder;
import org.elasticsearch.index.query.Operator;
import org.elasticsearch.index.query.QueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.script.Script;
import org.springframework.util.Assert;

import com.alibaba.fastjson.JSONObject;
import com.mongodb.client.model.Filters;

/**
 * 组合查询时，查询条件的操作符
 */
public enum ConditionOperatorEnum {
    EQUAL("等于"), WILDCARD("通配"), MATCH("匹配"), PREFIX("前缀"), IN("多个单词"), GT("大于"), NOT_LT("不小于"), LT("小于"), NOT_GT(
            "不大于"), RANGE("范围"), EXISTS("存在属性"), NOTEXISTS("不存在属性"), ISNULL("空值"), NOTNULL("空值");

    private final String desc;

    public String getDesc() {
        return desc;
    }

    ConditionOperatorEnum(String value) {
        this.desc = value;
    }

    private void setOptions(MatchQueryBuilder match, JSONObject options) {
        if (options.containsKey("minimumShouldMatch")) {
            match.minimumShouldMatch(options.getString("minimumShouldMatch"));
        }
        if (options.containsKey("operator")) {
            match.operator(Operator.valueOf(options.getString("operator")));
        } else {
            match.operator(Operator.AND);
        }
    }

    private void assertOneValue(Condition c) {
        Assert.isTrue(c.getValue() != null && c.getValue().length == 1,
                "字段" + c.getField() + "的过滤条件" + this.name() + "的value个数错误，应为1");
    }

    private void assertTwoValue(Condition c) {
        Assert.isTrue(c.getValue() != null && c.getValue().length == 2,
                "字段" + c.getField() + "的过滤条件" + this.name() + "的value个数错误，应为2");
    }

    private void assertNoValue(Condition c) {
        Assert.isTrue(c.getValue() == null || c.getValue().length == 0,
                "字段" + c.getField() + "的过滤条件" + this.name() + "的value个数错误，应为0或空");
    }

    public QueryBuilder esFilter(Condition c) {
        ColumnType type = ColumnType.valueOfType(c.getType());
        JSONObject options = new JSONObject(c.getOptions() != null ? c.getOptions() : Collections.emptyMap());
        QueryBuilder ret = null;
        String field = ColumnType.text_keyword.equals(type) ? (c.getField() + ".keyword") : c.getField();
        switch (this) {
            case EQUAL:
                this.assertOneValue(c);
                ret = QueryBuilders.termQuery(field, c.getValue()[0]);
                break;
            case MATCH:
                this.assertOneValue(c);
                MatchQueryBuilder match = QueryBuilders.matchQuery(c.getField(), ("" + c.getValue()[0]));
                this.setOptions(match, options);
                ret = match;
                break;
            case WILDCARD:
                this.assertOneValue(c);
                String value = c.getValue()[0].toString();
                if (!value.contains("*")) {
                    value = "*" + value + "*";
                }
                ret = QueryBuilders.wildcardQuery(field, value);
                break;
            case IN:
                this.assertOneValue(c);
                ret = QueryBuilders.termsQuery(field, Arrays.asList((c.getValue()[0] + "").replace('　', ' ').split("\\s")));
                break;
            case PREFIX:
                this.assertOneValue(c);
                ret = QueryBuilders.prefixQuery(field, "" + c.getValue()[0]);
                break;
            case GT:
                this.assertOneValue(c);
                ret = QueryBuilders.rangeQuery(field).gt(c.getValue()[0]);
                break;
            case NOT_LT:
                this.assertOneValue(c);
                ret = QueryBuilders.rangeQuery(field).gte(c.getValue()[0]);
                break;
            case LT:
                this.assertOneValue(c);
                ret = QueryBuilders.rangeQuery(field).lt(c.getValue()[0]);
                break;
            case NOT_GT:
                this.assertOneValue(c);
                ret = QueryBuilders.rangeQuery(field).lte(c.getValue()[0]);
                break;
            case RANGE:
                this.assertTwoValue(c);
                ret = QueryBuilders.rangeQuery(field).gte(c.getValue()[0]).lte(c.getValue()[1]);
                break;
            case EXISTS:
                this.assertNoValue(c);
                return QueryBuilders.existsQuery(field);
            case NOTEXISTS:
                this.assertNoValue(c);
                return QueryBuilders.boolQuery().mustNot(QueryBuilders.existsQuery(field));
            case ISNULL:
                this.assertNoValue(c);
                Script script = new Script("doc['" + field + "']==null || doc['" + field + "'].length<2");
                return QueryBuilders.boolQuery().should(QueryBuilders.boolQuery().mustNot(QueryBuilders.existsQuery(field)))
                        .should(QueryBuilders.scriptQuery(script));
            default:
                throw new IllegalArgumentException("不支持的操作:" + this.getDesc());
        }
        if (StringUtil.isNotEmpty(c.getNestedPath())) {
            ret = QueryBuilders.nestedQuery(c.getNestedPath(), ret, ScoreMode.Avg);
        }
        return ret;
    }

    public String sqlFilter(Condition c) {
        String field = c.getField();
        switch (this) {
            case EQUAL:
                this.assertOneValue(c);
                return field + " = #{" + c.getId() + "_0" + "}";
            case MATCH:
                this.assertOneValue(c);
                return field + " like '%' || #{" + c.getId() + "_0" + "} || '%'";
            case WILDCARD:
                this.assertOneValue(c);
                String s = (String) c.getValue()[0];
                try {
                    if (s.startsWith("*") && s.endsWith("*")) {
                        return field + " like '%' || #{" + c.getId() + "_0" + "} || '%'";
                    } else if (s.startsWith("*")) {
                        return field + " like '%' || #{" + c.getId() + "_0" + "}";
                    } else if (s.endsWith("*")) {
                        return field + " like  #{" + c.getId() + "_0" + "} || '%'";
                    } else {
                        return field + " = #{" + c.getId() + "_0" + "}";
                    }
                } finally {
                    c.getValue()[0] = s.replaceAll("\\*", "");
                }

            case IN:
                this.assertOneValue(c);
                return field + " in ${" + c.getId() + "_0" + "}";
            case PREFIX:
                this.assertOneValue(c);
                return field + " like #{" + c.getId() + "_0" + "} || '%'";
            case GT:
                this.assertOneValue(c);
                return field + " > #{" + c.getId() + "_0" + "}";
            case NOT_LT:
                this.assertOneValue(c);
                return field + " >= #{" + c.getId() + "_0" + "}";
            case LT:
                this.assertOneValue(c);
                return field + " < #{" + c.getId() + "_0" + "}";
            case NOT_GT:
                this.assertOneValue(c);
                return field + " <= #{" + c.getId() + "_0" + "}";
            case RANGE:
                this.assertTwoValue(c);
                return field + " between #{" + c.getId() + "_0" + "} and #{" + c.getId() + "_1}";
            case EXISTS:
                throw new IllegalArgumentException("不支持的操作:" + this.getDesc());
            case NOTEXISTS:
                throw new IllegalArgumentException("不支持的操作:" + this.getDesc());
            case ISNULL:
                this.assertNoValue(c);
                return field + " is null";
            case NOTNULL:
                this.assertNoValue(c);
                return field + " is not null";
            default:
                throw new IllegalArgumentException("不支持的操作:" + this.getDesc());
        }
    }

    public Bson mongoFilter(Condition c) {
        String field = c.getField();
        switch (this) {
            case EQUAL:
                this.assertOneValue(c);
                return Filters.eq(field, c.getValue()[0]);
            case MATCH:
                this.assertOneValue(c);
                return Filters.regex(field, (String) c.getValue()[0]);
            case WILDCARD:
                this.assertOneValue(c);
                String s = (String) c.getValue()[0];
                if (s.startsWith("*") && s.endsWith("*")) {
                    return Filters.regex(field, s.replace('*', '\0'));
                } else if (s.startsWith("*")) {
                    return Filters.regex(field, s.replace('*', '\0') + "@");
                } else if (s.endsWith("*")) {
                    return Filters.regex(field, "^" + s.replace('*', '\0'));
                } else {
                    return Filters.regex(field, s.replace('*', '\0'));
                }
            case IN:
                this.assertOneValue(c);
                return Filters.in(field, Arrays.asList((c.getValue()[0] + "").replace('　', ' ').split("\\s")));
            case PREFIX:
                this.assertOneValue(c);
                return Filters.in(field, "^" + c.getValue()[0]);
            case GT:
                this.assertOneValue(c);
                return Filters.gt(field, c.getValue()[0]);
            case NOT_LT:
                this.assertOneValue(c);
                return Filters.gte(field, c.getValue()[0]);
            case LT:
                this.assertOneValue(c);
                return Filters.lt(field, c.getValue()[0]);
            case NOT_GT:
                this.assertOneValue(c);
                return Filters.lte(field, c.getValue()[0]);
            case RANGE:
                this.assertTwoValue(c);
                return Filters.and(Filters.gte(field, c.getValue()[0]), Filters.lte(field, c.getValue()[1]));
            case EXISTS:
                this.assertNoValue(c);
                return Filters.exists(field);
            case NOTEXISTS:
                this.assertNoValue(c);
                return Filters.not(Filters.exists(field));
            case ISNULL:
                this.assertNoValue(c);
                return Filters.or(Filters.not(Filters.exists(field)),
                        Filters.where("this." + field + "==null || this." + field + ".length==0"));
            case NOTNULL:
                this.assertNoValue(c);
                return Filters.where("this." + field + "!=null && this." + field + ".toString().length>0");
            default:
                throw new IllegalArgumentException("不支持的操作:" + this.getDesc());
        }
    }
}
